home *** CD-ROM | disk | FTP | other *** search
/ Clickx 115 / Clickx 115.iso / software / tools / windows / tails-i386-0.16.iso / live / filesystem.squashfs / usr / share / pyshared / xdg / Mime.py < prev    next >
Encoding:
Python Source  |  2010-06-23  |  13.8 KB  |  475 lines

  1. """
  2. This module is based on a rox module (LGPL):
  3.  
  4. http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
  5.  
  6. This module provides access to the shared MIME database.
  7.  
  8. types is a dictionary of all known MIME types, indexed by the type name, e.g.
  9. types['application/x-python']
  10.  
  11. Applications can install information about MIME types by storing an
  12. XML file as <MIME>/packages/<application>.xml and running the
  13. update-mime-database command, which is provided by the freedesktop.org
  14. shared mime database package.
  15.  
  16. See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
  17. information about the format of these files.
  18.  
  19. (based on version 0.13)
  20. """
  21.  
  22. import os
  23. import stat
  24. import fnmatch
  25.  
  26. import xdg.BaseDirectory
  27. import xdg.Locale
  28.  
  29. from xml.dom import Node, minidom, XML_NAMESPACE
  30.  
  31. FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
  32.  
  33. types = {}      # Maps MIME names to type objects
  34.  
  35. exts = None     # Maps extensions to types
  36. globs = None    # List of (glob, type) pairs
  37. literals = None # Maps liternal names to types
  38. magic = None
  39.  
  40. def _get_node_data(node):
  41.     """Get text of XML node"""
  42.     return ''.join([n.nodeValue for n in node.childNodes]).strip()
  43.  
  44. def lookup(media, subtype = None):
  45.     "Get the MIMEtype object for this type, creating a new one if needed."
  46.     if subtype is None and '/' in media:
  47.         media, subtype = media.split('/', 1)
  48.     if (media, subtype) not in types:
  49.         types[(media, subtype)] = MIMEtype(media, subtype)
  50.     return types[(media, subtype)]
  51.  
  52. class MIMEtype:
  53.     """Type holding data about a MIME type"""
  54.     def __init__(self, media, subtype):
  55.         "Don't use this constructor directly; use mime.lookup() instead."
  56.         assert media and '/' not in media
  57.         assert subtype and '/' not in subtype
  58.         assert (media, subtype) not in types
  59.  
  60.         self.media = media
  61.         self.subtype = subtype
  62.         self._comment = None
  63.  
  64.     def _load(self):
  65.         "Loads comment for current language. Use get_comment() instead."
  66.         resource = os.path.join('mime', self.media, self.subtype + '.xml')
  67.         for path in xdg.BaseDirectory.load_data_paths(resource):
  68.             doc = minidom.parse(path)
  69.             if doc is None:
  70.                 continue
  71.             for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
  72.                 lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
  73.                 goodness = 1 + (lang in xdg.Locale.langs)
  74.                 if goodness > self._comment[0]:
  75.                     self._comment = (goodness, _get_node_data(comment))
  76.                 if goodness == 2: return
  77.  
  78.     # FIXME: add get_icon method
  79.     def get_comment(self):
  80.         """Returns comment for current language, loading it if needed."""
  81.         # Should we ever reload?
  82.         if self._comment is None:
  83.             self._comment = (0, str(self))
  84.             self._load()
  85.         return self._comment[1]
  86.  
  87.     def __str__(self):
  88.         return self.media + '/' + self.subtype
  89.  
  90.     def __repr__(self):
  91.         return '[%s: %s]' % (self, self._comment or '(comment not loaded)')
  92.  
  93. class MagicRule:
  94.     def __init__(self, f):
  95.         self.next=None
  96.         self.prev=None
  97.  
  98.         #print line
  99.         ind=''
  100.         while True:
  101.             c=f.read(1)
  102.             if c=='>':
  103.                 break
  104.             ind+=c
  105.         if not ind:
  106.             self.nest=0
  107.         else:
  108.             self.nest=int(ind)
  109.  
  110.         start=''
  111.         while True:
  112.             c=f.read(1)
  113.             if c=='=':
  114.                 break
  115.             start+=c
  116.         self.start=int(start)
  117.         
  118.         hb=f.read(1)
  119.         lb=f.read(1)
  120.         self.lenvalue=ord(lb)+(ord(hb)<<8)
  121.  
  122.         self.value=f.read(self.lenvalue)
  123.  
  124.         c=f.read(1)
  125.         if c=='&':
  126.             self.mask=f.read(self.lenvalue)
  127.             c=f.read(1)
  128.         else:
  129.             self.mask=None
  130.  
  131.         if c=='~':
  132.             w=''
  133.             while c!='+' and c!='\n':
  134.                 c=f.read(1)
  135.                 if c=='+' or c=='\n':
  136.                     break
  137.                 w+=c
  138.             
  139.             self.word=int(w)
  140.         else:
  141.             self.word=1
  142.  
  143.         if c=='+':
  144.             r=''
  145.             while c!='\n':
  146.                 c=f.read(1)
  147.                 if c=='\n':
  148.                     break
  149.                 r+=c
  150.             #print r
  151.             self.range=int(r)
  152.         else:
  153.             self.range=1
  154.  
  155.         if c!='\n':
  156.             raise ValueError('Malformed MIME magic line')
  157.  
  158.     def getLength(self):
  159.         return self.start+self.lenvalue+self.range
  160.  
  161.     def appendRule(self, rule):
  162.         if self.nest<rule.nest:
  163.             self.next=rule
  164.             rule.prev=self
  165.  
  166.         elif self.prev:
  167.             self.prev.appendRule(rule)
  168.         
  169.     def match(self, buffer):
  170.         if self.match0(buffer):
  171.             if self.next:
  172.                 return self.next.match(buffer)
  173.             return True
  174.  
  175.     def match0(self, buffer):
  176.         l=len(buffer)
  177.         for o in range(self.range):
  178.             s=self.start+o
  179.             e=s+self.lenvalue
  180.             if l<e:
  181.                 return False
  182.             if self.mask:
  183.                 test=''
  184.                 for i in range(self.lenvalue):
  185.                     c=ord(buffer[s+i]) & ord(self.mask[i])
  186.                     test+=chr(c)
  187.             else:
  188.                 test=buffer[s:e]
  189.  
  190.             if test==self.value:
  191.                 return True
  192.  
  193.     def __repr__(self):
  194.         return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest,
  195.                                   self.start,
  196.                                   self.lenvalue,
  197.                                   `self.value`,
  198.                                   `self.mask`,
  199.                                   self.word,
  200.                                   self.range)
  201.  
  202. class MagicType:
  203.     def __init__(self, mtype):
  204.         self.mtype=mtype
  205.         self.top_rules=[]
  206.         self.last_rule=None
  207.  
  208.     def getLine(self, f):
  209.         nrule=MagicRule(f)
  210.  
  211.         if nrule.nest and self.last_rule:
  212.             self.last_rule.appendRule(nrule)
  213.         else:
  214.             self.top_rules.append(nrule)
  215.  
  216.         self.last_rule=nrule
  217.  
  218.         return nrule
  219.  
  220.     def match(self, buffer):
  221.         for rule in self.top_rules:
  222.             if rule.match(buffer):
  223.                 return self.mtype
  224.  
  225.     def __repr__(self):
  226.         return '<MagicType %s>' % self.mtype
  227.     
  228. class MagicDB:
  229.     def __init__(self):
  230.         self.types={}   # Indexed by priority, each entry is a list of type rules
  231.         self.maxlen=0
  232.  
  233.     def mergeFile(self, fname):
  234.         f=file(fname, 'r')
  235.         line=f.readline()
  236.         if line!='MIME-Magic\0\n':
  237.             raise ValueError('Not a MIME magic file')
  238.  
  239.         while True:
  240.             shead=f.readline()
  241.             #print shead
  242.             if not shead:
  243.                 break
  244.             if shead[0]!='[' or shead[-2:]!=']\n':
  245.                 raise ValueError('Malformed section heading')
  246.             pri, tname=shead[1:-2].split(':')
  247.             #print shead[1:-2]
  248.             pri=int(pri)
  249.             mtype=lookup(tname)
  250.  
  251.             try:
  252.                 ents=self.types[pri]
  253.             except:
  254.                 ents=[]
  255.                 self.types[pri]=ents
  256.  
  257.             magictype=MagicType(mtype)
  258.             #print tname
  259.  
  260.             #rline=f.readline()
  261.             c=f.read(1)
  262.             f.seek(-1, 1)
  263.             while c and c!='[':
  264.                 rule=magictype.getLine(f)
  265.                 #print rule
  266.                 if rule and rule.getLength()>self.maxlen:
  267.                     self.maxlen=rule.getLength()
  268.  
  269.                 c=f.read(1)
  270.                 f.seek(-1, 1)
  271.  
  272.             ents.append(magictype)
  273.             #self.types[pri]=ents
  274.             if not c:
  275.                 break
  276.  
  277.     def match_data(self, data, max_pri=100, min_pri=0):
  278.         pris=self.types.keys()
  279.         pris.sort(lambda a, b: -cmp(a, b))
  280.         for pri in pris:
  281.             #print pri, max_pri, min_pri
  282.             if pri>max_pri:
  283.                 continue
  284.             if pri<min_pri:
  285.                 break
  286.             for type in self.types[pri]:
  287.                 m=type.match(data)
  288.                 if m:
  289.                     return m
  290.         
  291.  
  292.     def match(self, path, max_pri=100, min_pri=0):
  293.         try:
  294.             buf=file(path, 'r').read(self.maxlen)
  295.             return self.match_data(buf, max_pri, min_pri)
  296.         except:
  297.             pass
  298.  
  299.         return None
  300.     
  301.     def __repr__(self):
  302.         return '<MagicDB %s>' % self.types
  303.             
  304.  
  305. # Some well-known types
  306. text = lookup('text', 'plain')
  307. inode_block = lookup('inode', 'blockdevice')
  308. inode_char = lookup('inode', 'chardevice')
  309. inode_dir = lookup('inode', 'directory')
  310. inode_fifo = lookup('inode', 'fifo')
  311. inode_socket = lookup('inode', 'socket')
  312. inode_symlink = lookup('inode', 'symlink')
  313. inode_door = lookup('inode', 'door')
  314. app_exe = lookup('application', 'executable')
  315.  
  316. _cache_uptodate = False
  317.  
  318. def _cache_database():
  319.     global exts, globs, literals, magic, _cache_uptodate
  320.  
  321.     _cache_uptodate = True
  322.  
  323.     exts = {}       # Maps extensions to types
  324.     globs = []      # List of (glob, type) pairs
  325.     literals = {}   # Maps liternal names to types
  326.     magic = MagicDB()
  327.  
  328.     def _import_glob_file(path):
  329.         """Loads name matching information from a MIME directory."""
  330.         for line in file(path):
  331.             if line.startswith('#'): continue
  332.             line = line[:-1]
  333.  
  334.             type_name, pattern = line.split(':', 1)
  335.             mtype = lookup(type_name)
  336.  
  337.             if pattern.startswith('*.'):
  338.                 rest = pattern[2:]
  339.                 if not ('*' in rest or '[' in rest or '?' in rest):
  340.                     exts[rest] = mtype
  341.                     continue
  342.             if '*' in pattern or '[' in pattern or '?' in pattern:
  343.                 globs.append((pattern, mtype))
  344.             else:
  345.                 literals[pattern] = mtype
  346.  
  347.     for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')):
  348.         _import_glob_file(path)
  349.     for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
  350.         magic.mergeFile(path)
  351.  
  352.     # Sort globs by length
  353.     globs.sort(lambda a, b: cmp(len(b[0]), len(a[0])))
  354.  
  355. def get_type_by_name(path):
  356.     """Returns type of file by its name, or None if not known"""
  357.     if not _cache_uptodate:
  358.         _cache_database()
  359.  
  360.     leaf = os.path.basename(path)
  361.     if leaf in literals:
  362.         return literals[leaf]
  363.  
  364.     lleaf = leaf.lower()
  365.     if lleaf in literals:
  366.         return literals[lleaf]
  367.  
  368.     ext = leaf
  369.     while 1:
  370.         p = ext.find('.')
  371.         if p < 0: break
  372.         ext = ext[p + 1:]
  373.         if ext in exts:
  374.             return exts[ext]
  375.     ext = lleaf
  376.     while 1:
  377.         p = ext.find('.')
  378.         if p < 0: break
  379.         ext = ext[p+1:]
  380.         if ext in exts:
  381.             return exts[ext]
  382.     for (glob, mime_type) in globs:
  383.         if fnmatch.fnmatch(leaf, glob):
  384.             return mime_type
  385.         if fnmatch.fnmatch(lleaf, glob):
  386.             return mime_type
  387.     return None
  388.  
  389. def get_type_by_contents(path, max_pri=100, min_pri=0):
  390.     """Returns type of file by its contents, or None if not known"""
  391.     if not _cache_uptodate:
  392.         _cache_database()
  393.  
  394.     return magic.match(path, max_pri, min_pri)
  395.  
  396. def get_type_by_data(data, max_pri=100, min_pri=0):
  397.     """Returns type of the data"""
  398.     if not _cache_uptodate:
  399.         _cache_database()
  400.  
  401.     return magic.match_data(data, max_pri, min_pri)
  402.  
  403. def get_type(path, follow=1, name_pri=100):
  404.     """Returns type of file indicated by path.
  405.     path     - pathname to check (need not exist)
  406.     follow   - when reading file, follow symbolic links
  407.     name_pri - Priority to do name matches.  100=override magic"""
  408.     if not _cache_uptodate:
  409.         _cache_database()
  410.     
  411.     try:
  412.         if follow:
  413.             st = os.stat(path)
  414.         else:
  415.             st = os.lstat(path)
  416.     except:
  417.         t = get_type_by_name(path)
  418.         return t or text
  419.  
  420.     if stat.S_ISREG(st.st_mode):
  421.         t = get_type_by_contents(path, min_pri=name_pri)
  422.         if not t: t = get_type_by_name(path)
  423.         if not t: t = get_type_by_contents(path, max_pri=name_pri)
  424.         if t is None:
  425.             if stat.S_IMODE(st.st_mode) & 0111:
  426.                 return app_exe
  427.             else:
  428.                 return text
  429.         return t
  430.     elif stat.S_ISDIR(st.st_mode): return inode_dir
  431.     elif stat.S_ISCHR(st.st_mode): return inode_char
  432.     elif stat.S_ISBLK(st.st_mode): return inode_block
  433.     elif stat.S_ISFIFO(st.st_mode): return inode_fifo
  434.     elif stat.S_ISLNK(st.st_mode): return inode_symlink
  435.     elif stat.S_ISSOCK(st.st_mode): return inode_socket
  436.     return inode_door
  437.  
  438. def install_mime_info(application, package_file):
  439.     """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
  440.     If package_file is None, install <app_dir>/<application>.xml.
  441.     If already installed, does nothing. May overwrite an existing
  442.     file with the same name (if the contents are different)"""
  443.     application += '.xml'
  444.  
  445.     new_data = file(package_file).read()
  446.  
  447.     # See if the file is already installed
  448.     package_dir = os.path.join('mime', 'packages')
  449.     resource = os.path.join(package_dir, application)
  450.     for x in xdg.BaseDirectory.load_data_paths(resource):
  451.         try:
  452.             old_data = file(x).read()
  453.         except:
  454.             continue
  455.         if old_data == new_data:
  456.             return  # Already installed
  457.  
  458.     global _cache_uptodate
  459.     _cache_uptodate = False
  460.  
  461.     # Not already installed; add a new copy
  462.     # Create the directory structure...
  463.     new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application)
  464.  
  465.     # Write the file...
  466.     file(new_file, 'w').write(new_data)
  467.  
  468.     # Update the database...
  469.     command = 'update-mime-database'
  470.     if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')):
  471.         os.unlink(new_file)
  472.         raise Exception("The '%s' command returned an error code!\n" \
  473.                   "Make sure you have the freedesktop.org shared MIME package:\n" \
  474.                   "http://standards.freedesktop.org/shared-mime-info/" % command)
  475.